#ifndef AVALON_ASYNC_HPP
#define AVALON_ASYNC_HPP
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/exception_ptr.hpp>
#include <boost/signals2.hpp>
#pragma warning(disable : 4267)
#include <boost/thread.hpp>
#undef ERROR
#pragma warning(default : 4267)
#include "Avalon/Threading/Threading.hpp"
#include "Avalon/Threading/TimeoutException.hpp"

namespace Avalon {
namespace Threading {

  /*! \class BaseAsync
   *  \brief Base class for all Asyncs.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  class BaseAsync {
    public:

      //! Signals that the Async is no longer PENDING.
      typedef boost::signals2::signal<void ()> FinishedSignal;

      /*! \enum State
       *  \brief Enumerates the states of an operation.
       */
      enum State {

        //! The operation is currently pending.
        PENDING = 1,

        //! The operation succeeded.
        COMPLETE = 2,

        //! The operation resulted in an exception.
        EXCEPTION = 4,
      };

      virtual ~BaseAsync();

      //! Returns the State of the operation.
      virtual State GetState() = 0;

      //! Waits for the operation to finish.
      /*!
        \return The State of the operation.
      */
      template<typename Lock>
      State Wait(Lock& lock) {
        boost::unique_lock<boost::mutex> waitLock(this->m_mutex);
        if(this->m_state != PENDING) {
          return this->m_state;
        }
        lock.unlock();
        m_isFinishedCondition.wait(waitLock);
        lock.lock();
        assert(this->m_state != PENDING);
        return this->m_state;
      }

      //! Waits for the operation to finish.
      /*!
        \param timeout The amount of time to wait.
        \return The State of the operation.
      */
      virtual State Wait(boost::posix_time::time_duration timeout) = 0;

      //! Connects to the FinishedSignal.
      /*!
        \param slot The slot to connect.
        \return The <i>slot</i>'s connection to the signal.
      */
      virtual boost::signals2::connection ConnectFinishedSignal(
        const FinishedSignal::slot_type& slot) = 0;

      //! Connects to the FinishedSignal.
      /*!
        \param slot The slot to connect.
        \param state [out] The current state of the operation.
        \return The <i>slot</i>'s connection to the signal.
      */
      virtual boost::signals2::connection ConnectFinishedSignal(
        const FinishedSignal::slot_type& slot, State* state) = 0;

    protected:
      boost::mutex m_mutex;
      boost::condition_variable m_isFinishedCondition;
      State m_state;
      template<typename T> friend class AsyncOwner;

      //! Returns the exception held by this Async.
      virtual const boost::exception_ptr& GetException() = 0;
  };

  /*! \class Async
   *  \brief Represents the result of an asynchronous operation.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  template<typename T>
  class Async : public BaseAsync {
    public:

      //! Defines the shared pointer for this Async.
      typedef boost::shared_ptr<Async> Ptr;

      //! Signals that the Async is no longer PENDING.
      typedef BaseAsync::FinishedSignal FinishedSignal;

      virtual ~Async() {};

      //! Returns the result of this Async.
      /*!
        \return The result of this Async.
        \throw std::exception if the operation failed.
      */
      virtual const T& Get() = 0;

      //! Returns the result of this Async.
      /*!
        \param timeout The amount of time to wait for the result.
        \return The result of this Async.
        \throw TimeoutException if the timeout expired.
        \throw std::exception if the operation failed.
      */
      virtual const T& Get(boost::posix_time::time_duration timeout) = 0;
  };

  /*! \class Async
   *  \brief Represents the result of an asynchronous operation.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  template<>
  class Async<void> : public BaseAsync {
    public:

      //! Defines the shared pointer for this Async.
      typedef boost::shared_ptr<Async> Ptr;

      //! Signals that the Async is no longer PENDING.
      typedef BaseAsync::FinishedSignal FinishedSignal;

      virtual ~Async();

      //! Returns the result of this Async.
      /*!
        \throw std::exception if the result failed.
      */
      virtual void Get() = 0;

      //! Returns the result of this Async.
      /*!
        \param timeout The amount of time to wait for the result.
        \throw TimeoutException if the timeout expired.
        \throw std::exception if the operation failed.
      */
      virtual void Get(boost::posix_time::time_duration timeout) = 0;
  };

  /*! \class BaseAsyncOwner
   *  \brief Interface for an AsyncOwner.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  class BaseAsyncOwner {
    public:

      virtual ~BaseAsyncOwner();

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      virtual void SetException(const boost::exception_ptr& e) = 0;

      //! Copies the exception held by another async.
      /*!
        \param async The async to copy the exception from.
      */
      virtual void CopyException(const BaseAsyncPtr& async) = 0;
  };

  /*! \class AsyncOwner
   *  \brief Used to set the result of an Async.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  template<typename T>
  class AsyncOwner : public Async<T>, public BaseAsyncOwner {
    public:

      //! Defines the shared pointer for this Async.
      typedef boost::shared_ptr<AsyncOwner> Ptr;

      //! Signals that the Async is no longer PENDING.
      typedef BaseAsync::FinishedSignal FinishedSignal;

      //! Signals a cancel request.
      typedef boost::signals2::signal<void ()> CancelSignal;

      //! Creates an AsyncOwner.
      static Ptr Create() {
        return Ptr(new AsyncOwner());
      }

      //! Creates an AsyncOwner preset to a result.
      /*!
        \param result The result.
      */
      static Ptr Create(const T& result) {
        return Ptr(new AsyncOwner(result));
      }

      virtual ~AsyncOwner() {
        if(m_result != NULL) {
          m_result->~T();
          operator delete(m_result, &m_storage);
        }
      }

      //! Sets the result of this Async.
      /*!
        \param result The result of this Async.
      */
      void SetResult(const T& result) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == BaseAsync::PENDING);
        m_result = new(&m_storage) T(result);
        SetState(BaseAsync::COMPLETE, lock);
      }

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      template<typename E>
      void SetException(const E& e) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == BaseAsync::PENDING);
        m_exception = boost::copy_exception(e);
        SetState(BaseAsync::EXCEPTION, lock);
      }

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      virtual void SetException(const boost::exception_ptr& e) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == BaseAsync::PENDING);
        m_exception = e;
        SetState(BaseAsync::EXCEPTION, lock);
      }

      //! Copies the exception held by another async.
      /*!
        \param async The async to copy the exception from.
      */
      virtual void CopyException(const BaseAsyncPtr& async) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == BaseAsync::PENDING);
        m_exception = async->GetException();
        SetState(BaseAsync::EXCEPTION, lock);
      }

      virtual const T& Get() {
        assert(this->m_state & (BaseAsync::COMPLETE | BaseAsync::EXCEPTION));
        if(this->m_state == BaseAsync::EXCEPTION) {
          boost::rethrow_exception(m_exception);
        }
        return *m_result;
      }

      virtual const T& Get(boost::posix_time::time_duration timeout) {
        if(Wait(timeout) == BaseAsync::PENDING) {
          BOOST_THROW_EXCEPTION(TimeoutException());
        }
        return Get();
      }

      virtual BaseAsync::State GetState() {
        assert(this->m_state != BaseAsync::PENDING);
        return this->m_state;
      }

      virtual BaseAsync::State Wait(boost::posix_time::time_duration timeout) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        if(this->m_state == BaseAsync::PENDING) {
          if(timeout == boost::posix_time::pos_infin) {
            this->m_isFinishedCondition.wait(lock);
            assert(this->m_state != BaseAsync::PENDING);
          } else {
            this->m_isFinishedCondition.timed_wait(lock, timeout);
          }
        }
        return this->m_state;
      }

      virtual boost::signals2::connection ConnectFinishedSignal(
          const FinishedSignal::slot_type& slot) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        if(this->m_state != BaseAsync::PENDING) {
          lock.unlock();
          slot();
          return boost::signals2::connection();
        }
        return m_finishedSignal->connect(slot);
      }

      virtual boost::signals2::connection ConnectFinishedSignal(
          const FinishedSignal::slot_type& slot, BaseAsync::State* state) {
        boost::lock_guard<boost::mutex> lock(this->m_mutex);
        *state = this->m_state;
        if(this->m_state != BaseAsync::PENDING) {
          return boost::signals2::connection();
        }
        return m_finishedSignal->connect(slot);
      }

    protected:
      using BaseAsync::m_mutex;
      using BaseAsync::m_isFinishedCondition;
      using BaseAsync::m_state;

      virtual const boost::exception_ptr& GetException() {
        assert(this->m_state == BaseAsync::EXCEPTION);
        return m_exception;
      }

    private:
      typedef typename boost::aligned_storage<sizeof(T),
        boost::alignment_of<T>::value>::type Storage;
      Storage m_storage;
      T* m_result;
      boost::exception_ptr m_exception;
      boost::scoped_ptr<FinishedSignal> m_finishedSignal;

      AsyncOwner()
          : m_result(NULL),
            m_finishedSignal(new FinishedSignal()) {
        this->m_state = BaseAsync::PENDING;
      }

      AsyncOwner(const T& result)
          : m_result(new(&m_storage) T(result)) {
        this->m_state = BaseAsync::COMPLETE;
      }

      void SetState(BaseAsync::State state,
          boost::unique_lock<boost::mutex>& lock) {
        assert(this->m_state == BaseAsync::PENDING);
        assert(state != BaseAsync::PENDING);
        this->m_state = state;
        this->m_isFinishedCondition.notify_all();
        lock.unlock();
        (*m_finishedSignal)();
        m_finishedSignal.reset();
      }
  };

  /*! \class AsyncOwner
   *  \brief Used to set the result of an Async.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  template<>
  class AsyncOwner<void> : public Async<void>, public BaseAsyncOwner {
    public:

      //! Defines the shared pointer for this Async.
      typedef boost::shared_ptr<AsyncOwner> Ptr;

      //! Signals that the Async is no longer PENDING.
      typedef BaseAsync::FinishedSignal FinishedSignal;

      //! Signals a cancel request.
      typedef boost::signals2::signal<void ()> CancelSignal;

      //! Creates an AsyncOwner.
      static Ptr Create();

      //! Creates an already complete AsyncOwner.
      static Ptr CreateComplete();

      virtual ~AsyncOwner();

      //! Sets the state of this Async to COMPLETE.
      void SetComplete();

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      template<typename E>
      void SetException(const E& e) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == PENDING);
        m_exception = boost::copy_exception(e);
        SetState(EXCEPTION, lock);
      }

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      virtual void SetException(const boost::exception_ptr& e);

      //! Copies the exception held by another async.
      /*!
        \param async The async to copy the exception from.
      */
      virtual void CopyException(const BaseAsyncPtr& async);

      virtual void Get();

      virtual void Get(boost::posix_time::time_duration timeout);

      virtual BaseAsync::State GetState();

      virtual BaseAsync::State Wait(boost::posix_time::time_duration timeout);

      virtual boost::signals2::connection ConnectFinishedSignal(
          const FinishedSignal::slot_type& slot);

      virtual boost::signals2::connection ConnectFinishedSignal(
          const FinishedSignal::slot_type& slot, BaseAsync::State* state);

    protected:
      using BaseAsync::m_mutex;
      using BaseAsync::m_isFinishedCondition;
      using BaseAsync::m_state;

      virtual const boost::exception_ptr& GetException();

    private:
      boost::exception_ptr m_exception;
      boost::scoped_ptr<FinishedSignal> m_finishedSignal;

      AsyncOwner();
      AsyncOwner(int dummy);
      void SetState(BaseAsync::State state,
        boost::unique_lock<boost::mutex>& lock);
  };

  //! Makes a new Async whose result is already available.
  /*!
    \param result The result of the operation.
  */
  template<typename T>
  typename Async<T>::Ptr NewAsync(const T& result) {
    return AsyncOwner<T>::Create(result);
  }

  //! Makes a new Async that's already complete.
  Async<void>::Ptr NewAsync();
}
}

#endif // AVALON_ASYNC_HPP
